{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用的数据集，字典使用从 1 到 5 的评分，体验每位影评者对于某一个给定影片的喜爱程度。\n",
    "```python\n",
    "{\n",
    "    \"Lisa Rose\": {\n",
    "        \"Lady in the Water\": 2.5,\n",
    "        \"Snakes on a Plane\": 3.5,\n",
    "        \"Just My Luck\": 3.0,\n",
    "        \"Superman Returns\": 3.5,\n",
    "        \"You, Me and Dupree\": 2.5,\n",
    "        \"The Night Listener\": 3.0\n",
    "    },\n",
    "    \"Gene Seymour\": {\n",
    "        \"Lady in the Water\": 3.0,\n",
    "        \"Snakes on a Plane\": 3.5,\n",
    "        \"Just My Luck\": 1.5,\n",
    "        \"Superman Returns\": 5.0,\n",
    "        \"The Night Listener\": 3.0,\n",
    "        \"You, Me and Dupree\": 3.5\n",
    "    },\n",
    "    \"Michael Phillips\": {\n",
    "        \"Lady in the Water\": 2.5,\n",
    "        \"Snakes on a Plane\": 3.0,\n",
    "        \"Superman Returns\": 3.5,\n",
    "        \"The Night Listener\": 4.0\n",
    "    },\n",
    "    \"Claudia Puig\": {\n",
    "        \"Snakes on a Plane\": 3.5,\n",
    "        \"Just My Luck\": 3.0,\n",
    "        \"The Night Listener\": 4.5,\n",
    "        \"Superman Returns\": 4.0,\n",
    "        \"You, Me and Dupree\": 2.5\n",
    "    },\n",
    "    \"Mick LaSalle\": {\n",
    "        \"Lady in the Water\": 3.0,\n",
    "        \"Snakes on a Plane\": 4.0,\n",
    "        \"Just My Luck\": 2.0,\n",
    "        \"Superman Returns\": 3.0,\n",
    "        \"The Night Listener\": 3.0,\n",
    "        \"You, Me and Dupree\": 2.0\n",
    "    },\n",
    "    \"Jack Matthews\": {\n",
    "        \"Lady in the Water\": 3.0,\n",
    "        \"Snakes on a Plane\": 4.0,\n",
    "        \"The Night Listener\": 3.0,\n",
    "        \"Superman Returns\": 5.0,\n",
    "        \"You, Me and Dupree\": 3.5\n",
    "    },\n",
    "    \"Toby\": {\n",
    "        \"Snakes on a Plane\": 4.5,\n",
    "        \"You, Me and Dupree\": 1.0,\n",
    "        \"Superman Returns\": 4.0\n",
    "    }\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4.5\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "1.0"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import recommendations\n",
    "print(recommendations.critics['Toby']['Snakes on a Plane'])\n",
    "\n",
    "recommendations.critics['Toby']['You, Me and Dupree']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 寻找相近的用户\n",
    "数据准备好之后，需要找到一种方法确定人们在影视品味方面的相似程度。需要将每个人与所有其他人进行对比，并计算他们的相似度。下面介绍两种相似度计算方法："
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用欧几里得距离计算相似度, 它使用评价物品作为坐标轴，然后将评价人绘制在图上，并计算他们之间的距离远近。\n",
    "\n",
    "![distance](./distance.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.0440306508910548"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from math import sqrt\n",
    "## 计算Toby 和 LaSalle 的距离\n",
    "sqrt(pow(4.5-4.2, 2) + pow(1-2, 2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.48922945434505577"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## 偏好越相似的人，距离越短，需要取反，对偏好越相似的人给出越大的值\n",
    "1/(1+sqrt(pow(4.5-4.2, 2) + pow(1-2, 2)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "sim_distance方法, 计算两人之间基于所有物品的评价的相似度\n",
    "```python\n",
    "# 返回一个有关person1与person2的基于距离的相似度评价\n",
    "def sim_distance(prefs, person1, person2):\n",
    "    # 得到shared_items的列表\n",
    "    si = {}\n",
    "    for item in prefs[person1]:\n",
    "        if item in prefs[person2]:\n",
    "            si[item] = 1\n",
    "\n",
    "    # 如果两者没有共同之处，则返回0\n",
    "    if len(si) == 0:\n",
    "        return 0\n",
    "\n",
    "    # 计算所有差值的平方和\n",
    "    sum_of_squares = sum([pow(prefs[person1][item] - prefs[person2][item], 2)\n",
    "                          for item in si.keys()])\n",
    "\n",
    "    return 1 / (1 + sqrt(sum_of_squares))\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.29429805508554946"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "recommendations.sim_distance(recommendations.critics, 'Lisa Rose', 'Gene Seymour')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用皮尔逊计算相似度，该计算方法是判断两组数据与某一直线拟合程度的一种度量，这种方法在数据不是很规范的时候，可以有更好的效果；\n",
    "\n",
    "![sim_pearson](./sim_pearson.png)\n",
    "\n",
    "直线绘制原则是尽可能的靠近图上所有点，被称作最佳拟合线。如果两个评论者对于所有影片的评分都相同，那么这条直线为对角线，并且会与所有的做标点相交，从而得到结果为 1的相似度。上图所示，两个评论者对于影片的评论不尽相同，因此相关系数大约为 0.4 左右。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.39605901719066977"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "recommendations.sim_pearson(recommendations.critics, 'Lisa Rose', 'Gene Seymour')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "pearson相似度计算方法首先会找出两位评价者都曾评价过的物品，然后计算两者的评分总和和平方和，并求出评分的乘积之和。最后，算法利用这些计算结果计算出 pearson 相关系数。\n",
    "sim_pearson具体方法如下：\n",
    "```python\n",
    "# 返回p1和p2的皮尔逊相关系数\n",
    "def sim_pearson(prefs, p1, p2):\n",
    "    # 得到双方都曾评价过的物品列表\n",
    "    si = {}\n",
    "    for item in prefs[p1]:\n",
    "        if item in prefs[p2]:\n",
    "            si[item] = 1\n",
    "\n",
    "    # 如果两者没有共同之处，则返回0\n",
    "    if len(si) == 0:\n",
    "        return 0\n",
    "\n",
    "    # 得到列表元素的个数\n",
    "    n = len(si)\n",
    "\n",
    "    # 对所有偏好求和\n",
    "    sum1 = sum([prefs[p1][it] for it in si])\n",
    "    sum2 = sum([prefs[p2][it] for it in si])\n",
    "\n",
    "    # 求平方和\n",
    "    sum1Sq = sum([pow(prefs[p1][it], 2) for it in si])\n",
    "    sum2Sq = sum([pow(prefs[p2][it], 2) for it in si])\n",
    "\n",
    "    # 求乘积之和\n",
    "    pSum = sum([prefs[p1][it] * prefs[p2][it] for it in si])\n",
    "\n",
    "    # 计算皮尔逊评价值\n",
    "    num = pSum - (sum1 * sum2 / n)\n",
    "    den = sqrt((sum1Sq - pow(sum1, 2) / n) * (sum2Sq - pow(sum2, 2) / n))\n",
    "    if den == 0:\n",
    "        return 0\n",
    "\n",
    "    r = num / den\n",
    "\n",
    "    return r\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 其他相似度计算方法\n",
    "相比于简单的欧氏距离相似度计算方法，Pearson相似度算法在”夸大分值”的情况下，有更好的表现效果。比如，某人总是倾向于给出比其他人更高的分值，而二者的分值之差又始终保持一致的情况下，Pearson 算法仍然可以较为准确的计算出准确的相似度度。\n",
    "所以，具体使用哪种相似度计算方法，需要考虑具体的实际情况和应用场景。\n",
    "常用的相似度计算方法还有：\n",
    "- Jaccard 系数\n",
    "- 曼哈顿距离算法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 为评论者打分\n",
    "既然已经有了对两个人进行比较的函数，就可以实现指定人员对每个人的评分，并找出最接近的匹配结果。\n",
    "`topMatches`方法会找到与自己相似品味的影评人列表。\n",
    "```python\n",
    "# Returns the best matches for person from the prefs dictionary.\n",
    "# Number of results and similarity function are optional params.\n",
    "def topMatches(prefs, person, n=5, similarity=sim_pearson):\n",
    "    scores = [(similarity(prefs, person, other), other)\n",
    "              for other in prefs if other != person]\n",
    "    scores.sort()\n",
    "    scores.reverse()\n",
    "    return scores[0:n]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(0.9912407071619299, 'Lisa Rose'),\n",
       " (0.9244734516419049, 'Mick LaSalle'),\n",
       " (0.8934051474415647, 'Claudia Puig'),\n",
       " (0.66284898035987, 'Jack Matthews'),\n",
       " (0.38124642583151164, 'Gene Seymour')]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "recommendations.topMatches(recommendations.critics, 'Toby', n=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 推荐物品\n",
    "能够找到一些品味相似的影评者并阅读他们的评论固然不错，但是我们真正想推荐的是个人感兴趣的影片。\n",
    "当然最简单的方法就是查找与自己品味相似的影评人，并从他们所喜欢的影片中找出一部自己还未看过的电影，但是这种方法过于随意，可能会出现部分影片其实影评人本人未进行评价，导致自己错过该影片的情况；也有可能某些影评人热衷于古怪评论，导致推荐影片自己根本不喜欢的情况发生。\n",
    "为了解决上述问题，可以通过一种加权的评价方法为影片打分，评论者的评分结果会进行先后排名。\n",
    "\n",
    "![1713173865169.png](http://md.nianxi.wang/uploads/1713173865169.png)\n",
    "\n",
    "上图中列出了每位评价者的相似度评价值，以及他们对于三步影片的评分情况，其中以S.x打头的列给出了乘以相似度评价值之后的相似度。相比于与我们不相近的人，那些与我们有相近品味的人将会对整体评价值做出更多的贡献。\n",
    "\n",
    "如果使用总计值进行排名计算，一部更多人评论的影片会对最终结果产生更大的影响。为了修正这个问题，可以通过除以Sim.Sum那一行（它代表了所有对这部电影有过评论的评论者的相似度之和）。具体方法定义如下：\n",
    "```python\n",
    "# Gets recommendations for a person by using a weighted average\n",
    "# of every other user's rankings\n",
    "def getRecommendations(prefs, person, similarity=sim_pearson):\n",
    "    totals = {}\n",
    "    simSums = {}\n",
    "    for other in prefs:\n",
    "        # don't compare me to myself\n",
    "        if other == person:\n",
    "            continue\n",
    "        sim = similarity(prefs, person, other)\n",
    "\n",
    "        # ignore scores of zero or lower\n",
    "        if sim <= 0: continue\n",
    "        for item in prefs[other]:\n",
    "            # only score movies I haven't seen yet\n",
    "            if item not in prefs[person] or prefs[person][item] == 0:\n",
    "                # Similarity * Score\n",
    "                totals.setdefault(item, 0)\n",
    "                totals[item] += prefs[other][item] * sim\n",
    "                # Sum of similarities\n",
    "                simSums.setdefault(item, 0)\n",
    "                simSums[item] += sim\n",
    "\n",
    "    # Create the normalized list\n",
    "    rankings = [(total / simSums[item], item) for item, total in totals.items()]\n",
    "\n",
    "    # Return the sorted list\n",
    "    rankings.sort()\n",
    "    rankings.reverse()\n",
    "    return rankings\n",
    "```\n",
    "针对每一次循环，它会计算由person参数所指定的人员与这些人的相似度。然后它会循环遍历所有打过分的项。以黑体显示的代码行说明了每一项的最终评价值的计算方法--用每一项的评价值乘以相似度，并将所得乘积累加起来。最后，我们将每个总计值除以相似度之和，借此对评价值进行归一化处理，然后返回一个经过排序的结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(3.3477895267131017, 'The Night Listener'),\n",
       " (2.8325499182641614, 'Lady in the Water'),\n",
       " (2.530980703765565, 'Just My Luck')]"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "recommendations.getRecommendations(recommendations.critics, 'Toby')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
